import * as THREE from 'three';
import TWEEN from '@tweenjs/tween.js';

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; //导入控制器模块，轨道控制器
//glb gltf 文件
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; //导入GLTF模块，模型解析器,根据文件格式来定
//obj文件 mtl材料文件
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
//打开fbx文件
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';

//选中渲染用
import { GUI } from './dat.gui.module.js';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';

export class Base3d {
  // 容器
  private container;
  // 镜头
  private camera;
  // 场景
  private scene;
  // 构件组
  private group;
  // 渲染器
  private renderer;
  // 控制器
  private controls;
  // 构件树
  private componentTree;
  // 组合渲染
  private composer;
  // 画线用（高亮展示）
  private outlinePass;
  private effectFXAA;
  private renderPass;
  // 选中的模型构件
  private selectedObjects;
  private maxHeight;
  // gui 用户操作窗
  private gui;
  //坐标轴
  private axis;

  // 画线参数
  private params = {
    edgeStrength: 3.0,
    edgeGlow: 0.0,
    edgeThickness: 1.0,
    pulsePeriod: 0,
    rolate: false,
    usePatternTexture: false,
    axisShow: false,
  };
  constructor(selector) {
    this.container = document.querySelector(selector);
    this.camera;
    this.scene;
    this.group = new THREE.Group();
    this.componentTree;
    this.renderer;
    this.controls;
    this.composer;
    this.outlinePass;
    this.effectFXAA;
    this.selectedObjects;
    this.maxHeight;
    this.gui;
    this.renderPass;
    this.axis;
    // 初始化基础场景
    this.init();
    // 赋活
    this.animate();
  }
  /**
   * ================================================
   * 初始化
   * ================================================
   */
  init() {
    this.componentTree = [];
    //初始化渲染器
    this.initRender();
    //初始化场景
    this.initScene();
    //初始化用户操作窗
    this.initGui();
    //监听场景大小改变，跳转渲染尺寸
    window.addEventListener('resize', this.onWindowResize.bind(this));
    // 添加鼠标点击事件
    this.renderer.domElement.addEventListener('dblclick', this.onMouseClick.bind(this));
    this.renderer.domElement.addEventListener('click', this.hideText.bind(this));
  }
  //初始化渲染
  initRender() {
    this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); //设置抗锯齿
    this.renderer.shadowMap.enabled = true; // 开启阴影
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    //设置屏幕像素比
    this.renderer.setPixelRatio(window.devicePixelRatio);
    //渲染的尺寸大小
    this.renderer.setSize(window.innerWidth, window.innerHeight);
    //色调映射
    this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
    //曝光
    this.renderer.toneMappingExposure = 1;

    this.renderer.outputEncoding = THREE.sHSVEncoding;
    // 清除背景⾊，透明背景
    this.renderer.setClearColor(0xffffff, 0);

    this.container.appendChild(this.renderer.domElement);
  }
  //初始化场景
  initScene() {
    this.scene = new THREE.Scene();
    //平行光
    const pxguang = new THREE.DirectionalLight(0xffffff);
    this.scene.add(pxguang);
    //环境光
    const ambient = new THREE.AmbientLight(0xffffff);
    this.scene.add(ambient);
    this.scene.background = null;
  }
  //初始化镜头
  initCamera() {
    const geometry = new THREE.BufferGeometry();
    //使用包围球的方式，设置模型自适应大小。
    geometry.computeBoundingSphere();
    const maxDiameter = geometry.boundingSphere.radius * 2;
    // 画布宽度是 包围球直径的 widthZoom倍。即包围球缩放widthZoom倍，宽度正好填满画布。
    const widthZoom = window.innerWidth / maxDiameter;
    // 画布高度是 包围球直径的 heightZoom倍。即包围球缩放heightZoom倍，高度正好填满画布。
    const heightZoom = window.innerHeight / maxDiameter;
    // 取两者缩放比例较小者，设置为正交相机的缩放倍数。

    if (widthZoom >= heightZoom) {
      this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.25,
        heightZoom,
      );
    } else {
      this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.25,
        widthZoom,
      );
    }
    this.camera.position.set(-1.8, 0.6, 20);
  }
  animateCamera(
    current1 = { x: 0, y: 0, z: 0 },
    target1 = { x: 1, y: 0, z: 1 },
    current2 = { x: 30, y: 30, z: 30 },
    target2 = { x: 31, y: 30, z: 31 },
  ) {
    const positionVar = {
      x1: current1.x,
      y1: current1.y,
      z1: current1.z,
      x2: target1.x,
      y2: target1.y,
      z2: target1.z,
    };
    //关闭控制器
    this.controls.enabled = false;
    const tween = new TWEEN.Tween(positionVar);
    tween.to(
      {
        x1: current2.x,
        y1: current2.y,
        z1: current2.z,
        x2: target2.x,
        y2: target2.y,
        z2: target2.z,
      },
      1000,
    );
    const self = this;
    tween.onUpdate(function () {
      self.camera.position.x = positionVar.x1;
      self.camera.position.y = positionVar.y1;
      self.camera.position.z = positionVar.z1;
      self.controls.target.x = positionVar.x2;
      self.controls.target.y = positionVar.y2;
      self.controls.target.z = positionVar.z2;
    });
    tween.start();
    tween.onComplete(function () {
      ///开启控制器
      self.controls.enabled = true;
    });
    tween.easing(TWEEN.Easing.Cubic.InOut);
  }
  initControls() {
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
  }
  initGui() {
    //创建GUI
    this.gui = new GUI();
    this.gui
      .add(this.params, 'edgeStrength', 0.01, 10)
      .name('描边宽度')
      .onChange((value) => {
        if (this.outlinePass) {
          this.outlinePass.edgeStrength = Number(value);
        }
      });

    this.gui
      .add(this.params, 'edgeGlow', 0.0, 1)
      .name('亮度')
      .onChange((value) => {
        if (this.outlinePass) {
          this.outlinePass.edgeGlow = Number(value);
        }
      });

    this.gui
      .add(this.params, 'edgeThickness', 1, 4)
      .name('边缘扩散')
      .onChange((value) => {
        if (this.outlinePass) {
          this.outlinePass.edgeThickness = Number(value);
        }
      });

    this.gui
      .add(this.params, 'pulsePeriod', 0.0, 5)
      .name('呼吸灯频率')
      .onChange((value) => {
        if (this.outlinePass) {
          this.outlinePass.pulsePeriod = Number(value);
        }
      });

    this.gui.add(this.params, 'rolate').name('旋转展示');

    this.gui
      .add(this.params, 'usePatternTexture')
      .name('着色器')
      .onChange((value) => {
        if (this.outlinePass) {
          this.outlinePass.usePatternTexture = value;
        }
      });
    this.gui
      .add(this.params, 'axisShow')
      .name('显示坐标轴')
      .onChange((value) => {
        this.axis.visible = value;
      });
    const paramsColor = {
      visibleEdgeColor: '#28bfe0',
      hiddenEdgeColor: '#f20707',
    };

    this.gui
      .addColor(paramsColor, 'visibleEdgeColor')
      .name('可见边界颜色')
      .onChange((value) => {
        if (this.outlinePass) {
          this.outlinePass.visibleEdgeColor.set(value);
        }
      });

    this.gui
      .addColor(paramsColor, 'hiddenEdgeColor')
      .name('隐藏线条颜色')
      .onChange((value) => {
        if (this.outlinePass) {
          this.outlinePass.hiddenEdgeColor.set(value);
        }
      });
  }
  initOutlineObj() {
    // 创建效果组合器对象，在该对象上添加后期处理通道
    this.composer = new EffectComposer(this.renderer);

    this.renderPass = new RenderPass(this.scene, this.camera);
    this.composer.addPass(this.renderPass);

    this.outlinePass = new OutlinePass(
      new THREE.Vector2(window.innerWidth, window.innerHeight),
      this.scene,
      this.camera,
    );
    //先初始化一下样式
    this.outlinePass.edgeStrength = 2;
    this.outlinePass.edgeGlow = 1;
    this.outlinePass.edgeThickness = 1;
    this.outlinePass.usePatternTexture = false;
    this.outlinePass.visibleEdgeColor.set('#28bfe0');
    this.outlinePass.hiddenEdgeColor.set('#f20707');
    this.outlinePass.clear = true;

    this.composer.addPass(this.outlinePass);

    const onLoad = (texture) => {
      this.outlinePass.patternTexture = texture;
      texture.wrapS = THREE.RepeatWrapping;
      texture.wrapT = THREE.RepeatWrapping;
    };

    //这个是着色器 主要变化构件材质
    const loader = new THREE.TextureLoader();

    loader.load('/resource/img/bim/tri_pattern.jpg', onLoad);

    this.effectFXAA = new ShaderPass(FXAAShader);
    this.effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
    this.effectFXAA.renderToScreen = true;
    this.composer.addPass(this.effectFXAA);
  }
  animate = () => {
    requestAnimationFrame(this.animate);
    const timer = performance.now();
    if (this.params.rolate) {
      this.group.rotation.y = timer * 0.0001;
    }
    if (this.controls) {
      this.controls.update();
    }
    if (this.composer) {
      this.composer.render();
    }
    try {
      TWEEN.update();
    } catch (error) {}
  };
  /**
   * ================================================
   * 事件
   * ================================================
   */
  //点击事件
  onMouseClick(event) {
    if (event.button == 0) {
      let x, y;
      const mouse = new THREE.Vector2();
      if (event.changedTouches) {
        x = event.changedTouches[0].pageX;
        y = event.changedTouches[0].pageY;
      } else {
        x = event.clientX;
        y = event.clientY;
      }

      const elContent = this.container.getBoundingClientRect();
      //非全屏模式下 处理鼠标点击偏移的问题
      mouse.x = ((x - elContent.left) / window.innerWidth) * 2 - 1;
      mouse.y = -((y - elContent.top) / window.innerHeight) * 2 + 1;

      const rayCaster = new THREE.Raycaster();
      rayCaster.setFromCamera(mouse, this.camera);
      //获取距离点击最近的构件
      const intersects = rayCaster.intersectObjects([this.scene], true);
      if (intersects.length > 0) {
        for (let i = 0; i < intersects.length; i++) {
          const selectedObject = intersects[i].object;
          if (selectedObject.isMesh) {
            this.addSelectedObject(selectedObject);
            this.outlinePass.selectedObjects = this.selectedObjects;

            //镜头平滑移动特效
            //目标点位置
            //获取目标构件
            const box2 = new THREE.Box3();
            box2.expandByObject(selectedObject);
            //按照目标构建的最大坐标处理镜头
            const cameranewposition = {
              x: box2.max.x + Math.abs(0.5 * box2.max.x),
              y: this.maxHeight + Math.abs(0.5 * this.maxHeight), //镜头高一点 取整个模型的最高点 上一个高点的位置
              z: box2.max.z + Math.abs(0.5 * box2.max.z),
            };
            this.showText(this.controls.target, box2.max, box2.min);
            this.animateCamera(
              this.camera.position,
              this.controls.target,
              cameranewposition,
              box2.max,
            );
            // this.showText(box2.max);
            return;
          }
          this.hideText();
          this.outlinePass.selectedObjects = [];
        }
        //判断是MESH再处理
      } else {
        //点击空白删除描边
        this.hideText();
        this.outlinePass.selectedObjects = [];
      }
    }
  }
  //将指定的三维坐标转为屏幕坐标
  transPosition(position) {
    const world_vector = new THREE.Vector3(position.x, position.y, position.z);
    const vector = world_vector.project(this.camera);
    const halfWidth = window.innerWidth / 2,
      halfHeight = window.innerHeight / 2;
    return {
      x: Math.round(vector.x * halfWidth + halfWidth),
      y: Math.round(-vector.y * halfHeight + halfHeight),
    };
  }
  showText(position, maxP, minP) {
    const windowPosition = this.transPosition(position);
    const left = windowPosition.x;
    const top = windowPosition.y;
    const div = document.getElementById('tag');
    div!.style.display = 'inline-block';
    div!.style.left = left + 'px';
    div!.style.top = top + 'px';
    let innerHtmlText = '';
    innerHtmlText += '<span style="color: white; font-size: 10px; padding: 5px">详情信息</span>';
    innerHtmlText +=
      '<p style="padding: 5px; margin-top: -3px">模型最大坐标：（x:' +
      maxP.x +
      ',y:' +
      maxP.y +
      ',z:' +
      maxP.z +
      '）</p>';
    innerHtmlText +=
      '<p style="padding: 5px; margin-top: -3px">模型最小坐标：（x:' +
      minP.x +
      ',y:' +
      minP.y +
      ',z:' +
      minP.z +
      '）</p>';
    div!.innerHTML = innerHtmlText;
  }
  hideText() {
    const div = document.getElementById('tag');
    div!.style.display = 'none';
  }
  //添加选中的构件
  addSelectedObject(object) {
    this.selectedObjects = [];
    this.selectedObjects.push(object);
  }
  // 窗口大小变化事件
  onWindowResize() {
    const width = window.innerWidth;
    const height = window.innerHeight;

    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();

    this.renderer.setSize(width, height);
    if (this.composer) {
      this.composer.setSize(width, height);
    }
    this.effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight);
  }
  /**
   * ================================================
   * 用户方法
   * ================================================
   */
  //加载模型
  setModel(type, uri1, uri2) {
    if (type === 1) {
      const loader = new GLTFLoader();
      loader.load(uri1, (gltf) => {
        this.initSenceModel(gltf.scene);
      });
    } else if (type === 2) {
      if (uri2) {
        const mtlLoader = new MTLLoader();
        mtlLoader.load(uri2, (mtl) => {
          mtl.preload();
          const objLoader = new OBJLoader();
          objLoader.setMaterials(mtl);
          // 加载模型
          objLoader.load(uri1, (gltf) => {
            this.initSenceModel(gltf);
          });
        });
      } else {
        const objLoader = new OBJLoader();
        // 加载模型
        objLoader.load(uri1, (gltf) => {
          this.initSenceModel(gltf);
        });
        this.scene.traverse((obj) => {
          if (obj instanceof THREE.Mesh) {
            obj.material.emissive = obj.material.color;
            obj.material.emissiveIntensity = 1;
            obj.material.emissiveMap = obj.material.map;
          }
        });
      }
    } else if (type === 3) {
      const fbxLoader = new FBXLoader();
      fbxLoader.load(uri1, (gltf) => {
        this.initSenceModel(gltf);
      });
    }
  }
  initSenceModel(gltfscenc) {
    //重新初始化场景 和镜头
    this.group = new THREE.Group();
    this.initScene();
    //初始化相机
    this.initCamera();
    //初始化控制器，控制摄像头,控制器一定要在渲染器后
    this.initControls();
    this.hideText();

    const object = gltfscenc;
    this.group.add(object);
    this.scene.add(this.group);
    //坐标轴
    this.axis = new THREE.AxesHelper(500);
    //默认不显示坐标轴
    this.axis.visible = false;
    this.group.add(this.axis);

    const boxObject = new THREE.Box3();
    boxObject.setFromObject(object);
    const mdlen = boxObject.max.x - boxObject.min.x; // 模型长度
    const mdwid = boxObject.max.z - boxObject.min.z; // 模型宽度
    const mdhei = boxObject.max.y - boxObject.min.y; // 模型高度
    const x1 = boxObject.min.x + mdlen / 2; // 模型中心点坐标X
    const y1 = boxObject.min.y + mdhei / 2; // 模型中心点坐标Y
    const z1 = boxObject.min.z + mdwid / 2; // 模型中心点坐标Z
    object.position.set(-x1, -y1, -z1); // 将模型进行偏移

    this.maxHeight = boxObject.max.y;

    this.getComponentTree(this.scene);
    this.initOutlineObj();
  }
  //递归寻找构件
  generateOptions(params) {
    const result = [];
    for (const param of params) {
      if (param.parent === undefined || param.parent === null || param.parent.type === 'Scene') {
        // 判断是否为顶层节点
        const parent = {
          key: param.uuid,
          title: param.name,
          children: [],
        };
        parent.children = this.getchilds(param.uuid, params); // 获取子节点
        result.push(parent as never);
      }
    }
    return result;
  }
  //寻找子节点
  getchilds(id, array) {
    const childs = [];
    for (const arr of array) {
      // 循环获取子节点
      if (arr.parent.uuid === id) {
        childs.push({
          key: arr.uuid,
          title: arr.name,
        });
      }
    }
    for (const child of childs) {
      // 获取子节点的子节点
      const childscopy = this.getchilds(child.key, array); // 递归获取子节点
      if (childscopy.length > 0) {
        child.children = childscopy;
      }
    }
    return childs;
  }
  //计算场景内构件树
  getComponentTree(view) {
    const scene = view;
    const components = [];
    for (let index = 0; index < scene.children.length; index++) {
      const object = scene.children[index];
      object.traverseVisible(function (object) {
        //光源不算构件
        if (!(object.type === 'AmbientLight' || object.type === 'DirectionalLight')) {
          components.push(object as never);
        }
      });
    }
    this.componentTree = this.generateOptions(components);
  }
  //获取构件树
  getCompoentTree() {
    return this.componentTree;
  }
  //重新渲染构件 对未选中的构件进行隐藏
  rerenderCom(selectItems) {
    //循环场景中的构件
    this.scene.traverse(function (obj) {
      //选中的构件中存在 则展示 否则隐藏
      if (selectItems && selectItems.indexOf(obj.uuid) > -1 && obj.type === 'Mesh') {
        // obj.material.transparent = false;
        // obj.material.opacity = 5;
        obj.visible = true;
      } else if (obj.type === 'Mesh') {
        // obj.material.transparent = true;
        // obj.material.opacity = 0.1;
        obj.visible = false;
      }
    });
  }
  //清除
  clear() {
    //循环场景中的构件
    this.group.clear();
    this.scene.clear();
    this.gui.destroy();
  }
}
